﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace Zhaoxi.SSQDemo
{
    /// <summary>
    /// Mechine.xaml 的交互逻辑
    /// </summary>
    public partial class Mechine : UserControl
    {
        /// <summary>
        /// 随机值生成对象
        /// </summary>
        Random random = new Random();

        /// <summary>
        /// 不同类型的球，总数  红色：33   蓝色：16
        /// </summary>
        public int BallNumber { get; set; }

        /// <summary>
        ///球号码的颜色
        /// </summary>
        public Brush BallColor { get; set; }

        //需要弹出的球的数量
        public int ResultBallCount { get; set; }



        /// <summary>
        /// 球号码的集合  
        /// 添加一个球，页面上需要显示球号码
        /// </summary>
        public ObservableCollection<BallModel> BallList { get; set; } =
            new ObservableCollection<BallModel>();



        //固定位置的，小球没有赋号码，初始的位置，红色求，初始33个位置，   蓝色球16个
        List<Point> points = new List<Point>();


        /// <summary>
        /// 定时器---可以配置时间，他按照配置的时间定时去执行一些逻辑
        /// </summary>
        DispatcherTimer dispatcherTimer = new DispatcherTimer();

        /// <summary>
        /// 构造函数
        /// </summary>
        public Mechine()
        {
            InitializeComponent();
            this.DataContext = this;


            this.Loaded += (se, ev) =>
            {
                //用户控件加载完毕后，执行的逻辑
                //在这里就可以初始化球号码---位置  数字 
                InitBalls();
            };

            ///间隔的时间
            dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 10);

            //每间隔多长时间后，要执行的逻辑
            dispatcherTimer.Tick += DispatcherTimer_Tick;
        }


        /// <summary>
        /// 恢复小球的初始状态
        /// </summary>
        private void InitBalls()
        {
            BallList.Clear();

            ///圆球的圆心坐标，并且保存到points列表中
            CalculatorBallLocations(BallNumber); //方法执行完毕，point集合中 就已经保存了球号码的坐标


            // 初始化 用作随机数索引种子
            List<int> ints = new List<int>();
            for (int i = 0; i < BallNumber; i++)
            {
                ints.Add(i);
            }

            for (int i = 0; i < BallNumber; i++)
            {
                int index = GetIndex(ints);
                BallModel model = new BallModel()
                {
                    Number = i + 1,
                    Direction = this.InitVector(10),
                    X = points[index].X - 15,
                    Y = points[index].Y - 15
                };
                BallList.Add(model);
            }

        }


        /// <summary>
        /// 初始化小球的初始运行向量
        /// </summary>
        /// <param name="speed"></param>
        /// <returns></returns>
        private Vector InitVector(int speed)
        {
            Vector vector = new Vector();
            double x = random.Next(-20, 20);
            double y = random.Next(5, 10) * -1;

            //
            vector = new Vector(x, y);
            vector.Normalize();

            vector *= speed;

            return vector;
        }


        private int GetIndex(List<int> ints)
        {
            int index = random.Next(0, ints.Count);
            int result = ints[index];

            ints.RemoveAt(index);
            return result;
        }

        /// <summary>
        /// 计算多个球在玻璃罩中个圆球的圆心坐标，并且保存到points列表中
        /// </summary>
        /// <param name="ball_number">要初始化多少个球号码   红色球和蓝色是不一样的</param>
        void CalculatorBallLocations(int ball_number)
        {
            points.Clear();
            points.Add(new Point(160, 305));


            while (points.Count < ball_number)
            {
                int count = points.Count;

                // 记录所有有效点
                List<Point> valid_points = new List<Point>();

                for (int i = Math.Max(0, count - 10); i < count; i++)
                {
                    Point point = points[i];

                    // 获取每个球向外扩展一个半径后与大圆向内一个小球半径的圆的交点
                    List<Location> big_circle_ps = CalculatorIntersection(
                        new Circle { X = point.X, Y = point.Y, R = 30 },
                        new Circle { X = big_circle.X, Y = big_circle.Y, R = 145 }
                        );

                    // 检查小球与其他小球向外扩展的交点
                    List<Location> small_ball_ps = new List<Location>();
                    for (int k = Math.Max(0, count - 11); k < count; k++)
                    {
                        var temp_p = points[k];
                        if (temp_p == point) continue;

                        List<Location> ppp = CalculatorIntersection(
                                        new Circle { X = point.X, Y = point.Y, R = 30 },
                                        new Circle { X = temp_p.X, Y = temp_p.Y, R = 30 }
                                    );

                        small_ball_ps.AddRange(ppp);
                    }

                    //整合所有的圆的点
                    big_circle_ps.AddRange(small_ball_ps);

                    //判断所有的点位是否是有效
                    CheckPointValid(big_circle_ps);

                    //得到有效点的集合
                    valid_points.AddRange(big_circle_ps.Where(l => l.IsValid).Select(l => l.Point).ToList());
                }

                // 从所有有效点里取出最下面（Y值最大）的坐标
                if (valid_points.Count == 1)
                {
                    points.Add(valid_points[0]);

                }   //如果存在最大的Y值，如果有就添加，如果没有就不添加
                else if (valid_points.Exists(tp => tp.Y == valid_points.Max(tp => tp.Y)))
                {
                    Point pp = valid_points.FirstOrDefault(p => p.Y == valid_points.Max(tp => tp.Y));
                    points.Add(pp);
                }

            }
        }


        /// <summary>
        /// 计算两圆交点
        /// </summary>
        /// <param name="circle1">第一个圆的信息，包括中心点坐标以及半径</param>
        /// <param name="circle2">第二个圆的信息，包括中心点坐标以及半径</param>
        List<Location> CalculatorIntersection(Circle circle1, Circle circle2)
        {
            var intersectionPoints = new List<Location>();

            // 两圆心的距离
            double distance = (new Point(circle1.X, circle1.Y) - new Point(circle2.X, circle2.Y)).Length;

            // 判断两圆处于相交的状态
            if (distance < circle1.R + circle2.R && distance > Math.Abs(circle1.R - circle2.R))
            {
                //两圆相交    （大圆的半径平方 减去 小圆的半径平方 加上 （2倍的大圆半径的平方） 除以两倍圆心距离；  得到的结果：

                double a = (Math.Pow(circle1.R, 2) - Math.Pow(circle2.R, 2) + Math.Pow(distance, 2)) / (2 * distance);

                //得到直角三角形的高
                double h = Math.Sqrt(Math.Pow(circle1.R, 2) - Math.Pow(a, 2));

                //计算得到 两个交点连线的 中点的 坐标值
                double x0 = circle1.X + a * (circle2.X - circle1.X) / distance;
                double y0 = circle1.Y + a * (circle2.Y - circle1.Y) / distance;

                //具体交点相对中点的偏移量
                double rx = -h * (circle2.Y - circle1.Y) / distance;
                double ry = h * (circle2.X - circle1.X) / distance;

                //根据偏移量计算出交点的  坐标值
                intersectionPoints.Add(new Location { Point = new Point(x0 + rx, y0 + ry) }); // 交点1 
                intersectionPoints.Add(new Location { Point = new Point(x0 - rx, y0 - ry) }); // 交点2
            }

            return intersectionPoints;
        }


        void CheckPointValid(List<Location> result_points)
        {
            foreach (Location corss_p in result_points)
            {
                // 在大圆之内
                if ((corss_p.Point - new Point(big_circle.X, big_circle.Y)).Length > 145)
                {
                    corss_p.IsValid = false;
                    continue;
                }

                // 检查所有计算出来的圆心点与其他小球之间的距离
                // 小于最小距离的丢弃
                for (int i = 0; i < points.Count; i++)
                {
                    var ball_p = points[i];
                    //if (temp_p == point) continue;

                    if (Math.Ceiling((ball_p - corss_p.Point).Length) < 30)
                    {
                        corss_p.IsValid = false;
                    }
                }
            }
        }


        /// <summary>
        /// 确定球号码做不同方向的运动
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DispatcherTimer_Tick(object? sender, EventArgs e)
        {
            for (int i = 0; i < BallList.Count; i++)
            {
                if (BallList[i].IsSelected)
                {
                    continue;
                }

                //选出某个小球的当前运行向量
                Vector vector = BallList[i].Direction;

                //小球根据向量去进行偏移，回弹运动 
                this.MoveBall(BallList[i], ref vector);

                //如果有偏移，就把偏移的向量回写回来；
                BallList[i].Direction = vector;
            }
        }


        /// <summary>
        /// 小球根据现有向量做偏移，回弹
        /// </summary>
        /// <param name="ball"></param>
        /// <param name="vector"></param>
        private void MoveBall(BallModel ball, ref Vector vector)
        {
            // 判断球位置
            double new_x = ball.X + vector.X;
            double new_y = ball.Y + vector.Y;

            if ((new Point(new_x + 15, new_y + 15) - new Point(162, 162)).Length >= 145)
            {
                ball.X = ball.X - vector.X;
                ball.Y = ball.Y - vector.Y;
                // 向量对称偏转
                var v1 = new Point(new_x + 15, new_y + 15) - new Point(162, 162);
                var new_angle = Vector.AngleBetween(v1, vector) * 2;

                new_angle = new_angle > 180 ? new_angle * -1 : new_angle;
                Matrix matx = new Matrix();
                matx.Rotate(new_angle);
                vector *= matx;
            }
            ball.X = ball.X + vector.X;
            ball.Y = ball.Y + vector.Y;
        }


        /// <summary>
        /// 开始了
        /// </summary>
        /// <param name="callback">回调：红色球逻辑处理完毕后，回调，就去执行篮色球的处理</param>
        public void Start(Func<bool> callback)
        {
            //触发旋转控制对象
            bool state = VisualStateManager.GoToElementState(this, "RunState", false);
            //运行定时器
            dispatcherTimer.Start();
            Task.Run(async () =>
            {
                //第一个小球最后的停止点坐标
                Point final_point = new Point(-112, 380);

                //开始去生成开奖的号码
                List<int> ints = new List<int>();
                for (int i = 0; i < BallNumber; i++)
                {
                    ints.Add(i);
                }

                int count = 0;
                while (count++ < ResultBallCount)
                {
                    await Task.Delay(4000);//每4秒 开始一波~

                    //确定球号码的索引值
                    int index = GetIndex(ints);


                    //设置选中的球---避免在圆盘中继续运动（支持视觉效果）
                    BallList[index].IsSelected = true;

                    //确定，选中球后，第一次需要运动点位坐标
                    Point p1 = new Point(145, 325);

                    //球号码到达p1之后，继续往下运行
                    Point p2 = new Point(145, 368);


                    // 两步运动
                    // 向下移动，修改纵坐标
                    for (double step = 325; step < 368; step += 1)
                    {
                        BallList[index].X = p1.X;
                        BallList[index].Y = p1.Y;

                        p1 = new Point(145, step);

                        //线程信号量等待；
                        await Task.Delay(10);
                    }

                    //计算 球从第二个点位 到第三个点位的向量值
                    Vector vector = final_point - p2;  //相比于第一个球的位置，做向上的偏移

                    //向量单位化
                    vector.Normalize();

                    //单位长度，增加
                    vector *= 10;

                    //根据向量开始移动
                    for (double x = 145, y = 365; x > final_point.X && y < final_point.Y;)
                    {
                        p2 += vector;

                        BallList[index].X = p2.X;
                        BallList[index].Y = p2.Y;

                        x = p2.X; y = p2.Y;

                        await Task.Delay(50);
                    }

                    //确定下一个球的最终停止位置；
                    final_point += new Vector(30, -1);
                }


                //上面执行完毕了，说明红色球的逻辑已经处理完成 
                //应该停止红色球的处理，开始蓝色球的处理


                callback?.Invoke();
            });
        }

        /// <summary>
        /// 停止生成球号码
        /// </summary>
        public void Stop()
        {
            dispatcherTimer.Stop();
            bool state = VisualStateManager.GoToElementState(this, "StopState", false);
            // 退出
            CalculatorBallLocations(BallNumber - ResultBallCount);

            int i = 0;
            foreach (var ball in BallList)
            {
                if (ball.IsSelected)
                {
                    continue;
                }
                ball.X = points[i].X - 15;
                ball.Y = points[i++].Y - 15;
            }
        }


        // 重置场景
        public void Reset()
        {
            InitBalls();
        }



        Circle big_circle = new Circle()
        {
            X = 160,
            Y = 160,
            R = 160
        };

    }

    public class Circle
    {
        public double X { get; set; } // 圆心x坐标
        public double Y { get; set; } // 圆心y坐标
        public double R { get; set; } // 圆的半径
    }
    public class Location
    {
        public Point Point { get; set; }
        public bool IsValid { get; set; } = true;
    }
}
